/*
 * Copyright (c) 2015 tingpan
 * Copyright 2012-2015 Senscom.cn
 *    tingpan <smbx-ztbz@cnblog.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 */

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/miscdevice.h>  //混杂设备
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/delay.h>  //mdelay
#include <linux/device.h>
#include <linux/gpio.h>
#include <linux/spi/spi.h>
#include <linux/spi/spi_gpio.h>

#include <linux/kfifo.h>
#include <linux/interrupt.h>
#include <linux/irq.h>

#include <linux/types.h>  //u8

#include <linux/ioctl.h>


#include <linux/module.h>  
#include <linux/kernel.h>  
#include <linux/init.h>  
#include <linux/proc_fs.h>  
#include <linux/jiffies.h>  
#include <asm/uaccess.h>  

#include <asm/addrspace.h>
#include <asm/tc3162/tc3162.h>
#include <asm/tc3162/ledcetrl.h>





#define PCF8574_DEBUG    1

#if (PCF8574_DEBUG == 1)
#define PRK(...) printk(__VA_ARGS__)
#else 
#define PRK(...) 
#endif

#define DRV_NAME    "pcf8574"
#define DRV_DESC    "use i2c to extend i/o" 
#define DRV_VERSION    "0.1.0"

//#define PCF8574_NODE_NAME DRV_NAME

/*
#define  GPIO_SDA  8
#define  GPIO_SLC  7
*/

#define  GPIO_SDA_1  8
#define  GPIO_SLC_1  7


#define  GPIO_SDA  6
#define  GPIO_SLC  4

#define  GPIO_8033_RESET  17
#define  GPIO_8337_RESET  22

#define  GPIO_BOMA_1  28
#define  GPIO_BOMA_2  23
#define  GPIO_BOMA_3  5
#define  GPIO_BOMA_4  25

#define  GPIO_SFP_R_WUGUANG_1  10
#define  GPIO_SFP_R_WUGUANG_2  21

#define  GPIO_SFP_SEND_1  38
#define  GPIO_SFP_SEND_2  11


int scl_pin = GPIO_SLC;
int sda_pin = GPIO_SDA;



static struct mutex pcf8574_lock;

static DEFINE_MUTEX(pcf8574_lock);

//pcf8574
// 发送I2C启动位 






#define MDIO 3  /* MDIO correspond PD21 */
#define MDC 2  /* MDC correspond PD20 */
#define MDIO_DELAY 50
#define MDIO_READ_DELAY 50

/*  Or MII_ADDR_C45 into regnum for read/write on mii_bus to enable the 21 bit 
 *   IEEE 802.3ae clause 45 addressing mode used by 10GIGE phy chips. 
 *   */ 
#define MII_ADDR_C45 (1<<30)

#define MDIO_READ 2
#define MDIO_WRITE 1

#define MDIO_C45 (1<<15)
#define MDIO_C45_ADDR (MDIO_C45 | 0)
#define MDIO_C45_READ (MDIO_C45 | 3)
#define MDIO_C45_WRITE (MDIO_C45 | 1)

#define MDIO_SETUP_TIME 10
#define MDIO_HOLD_TIME 10


//#define READ_REG 0x37
//#define WRITE_REG 0x38


#define MDIO_C45_TEST 0


typedef struct gpio_ctrl_blk{
    int pin;
    int value;
}gpio_cblk_t;

typedef struct phy_reg_blk{
    unsigned int phy_address;
    unsigned int reg_address;
    unsigned int reg_value;
}phy_reg_cblk_t;


#define MDIO_DEV_ID 't'
#define READ_REG             _IOWR (MDIO_DEV_ID,0x37,phy_reg_cblk_t)
#define WRITE_REG            _IOWR (MDIO_DEV_ID,0x38,phy_reg_cblk_t)
static void MDC_OUT(void);
static void MDIO_OUT(void);
static void MDIO_IN(void);
static void MDC_H(void);
static void MDC_L(void);
static int GET_MDIO(void);
static void SET_MDIO(int val);


#define LED_GET_GPIO_CTRL(x) ((x>31)?(regRead32(CR_GPIO_CTRL1)&(1<<(x-32))):(regRead32(CR_GPIO_CTRL)&(1<<x)))
#define LED_GET_GPIO_DATA(x) ((x>31)?(regRead32(CR_GPIO_DATA1)&(1<<(x-32))):(regRead32(CR_GPIO_DATA)&(1<<x)))


#define REG_CLRBITS(reg, bits) (regWrite32(reg, regRead32(reg) & ~(bits)))
#define REG_SETBITS(reg, bitsmask, bits) (regWrite32(reg, (regRead32(reg) & ~(bitsmask)) | (bits)))


#define GPIO_INPUT_ENABLE(x)		do { \	
									if(x > 31){							\
										if(x > 47)						\
											REG_CLRBITS(CR_GPIO_CTRL3, (0x00000003 << ((x-48)* 2)));	\
										else							\
											REG_CLRBITS(CR_GPIO_CTRL2, (0x00000003 << ((x-32)* 2))); \
										REG_CLRBITS(CR_GPIO_ODRAIN1, (0x00000001 << (x-32))); \
									}									\
									else{								\
										if(x > 15)						\
											REG_CLRBITS(CR_GPIO_CTRL1,(0x00000003 << ((x-16)* 2)));	\
										else							\
											REG_CLRBITS(CR_GPIO_CTRL,(0x00000003 << (x* 2)));	\
										REG_CLRBITS(CR_GPIO_ODRAIN,(0x00000001 << (x))); \
									}									\
				} while(0);



#define GPIO_OTPUT_MODE(x)		do { 	if(x > 15){ 					\
											REG_SETBITS(CR_GPIO_CTRL1,0x3<<((x-16)*2), 1<<((x-16)*2)); \
										}else{							\
											REG_SETBITS(CR_GPIO_CTRL,0x3<<((x)*2), 1<<((x)*2)); \
										}								\
										REG_SETBITS(CR_GPIO_ODRAIN, 0x1<<(x), 1<<(x)); \
                     		} while(0);

#define HIGH_NORMAL(x) do { regWrite32(CR_GPIO_DATA,regRead32(CR_GPIO_DATA) | (1<<x)); } while (0);
#define LOW_NORMAL(x) do {  regWrite32(CR_GPIO_DATA,regRead32(CR_GPIO_DATA) & ~(1<<x)); } while (0);

static int gpio_get_value_ok(int pin)
{
    if((regRead32(CR_GPIO_DATA) & (1<<pin)) == 0)
        return 0;
	else 
		return 1;
}


static void gpio_direction_output_value(int pin,int value)
{
  //  GPIO_OTPUT_MODE(pin);
	if( value == 0 )
	{
	    LOW_NORMAL(pin);
		
	}else {

	  HIGH_NORMAL(pin);
	  
	}
}

static void gpio_boma_input_init(void)
{
    GPIO_INPUT_ENABLE(GPIO_BOMA_1);
    GPIO_INPUT_ENABLE(GPIO_BOMA_2);
	GPIO_INPUT_ENABLE(GPIO_BOMA_3);
	GPIO_INPUT_ENABLE(GPIO_BOMA_4);

	GPIO_INPUT_ENABLE(GPIO_SFP_R_WUGUANG_1);
	GPIO_INPUT_ENABLE(GPIO_SFP_R_WUGUANG_2);	

	GPIO_OTPUT_MODE(GPIO_SLC);


	GPIO_OTPUT_MODE(GPIO_SFP_SEND_1);
	GPIO_OTPUT_MODE(GPIO_SFP_SEND_1);


	GPIO_OTPUT_MODE(GPIO_8337_RESET);
	HIGH_NORMAL(GPIO_8337_RESET);

	GPIO_OTPUT_MODE(GPIO_8033_RESET);
	HIGH_NORMAL(GPIO_8033_RESET);

}


static int op_pcf8574_init(void )
{

	gpio_boma_input_init();
	return 0;
}



/* 设置MDC为输出引脚，在MDC输出时钟之前设置 */
static void MDC_OUT(void)
{
    gpio_cblk_t gpio_dev;
    gpio_dev.pin = MDC;
    GPIO_OTPUT_MODE(gpio_dev.pin); 
}

/* 设置MDIO的gpio引脚为输出引脚 */
static void MDIO_OUT(void)
{
    gpio_cblk_t gpio_dev;
    gpio_dev.pin = MDIO;
    GPIO_OTPUT_MODE(gpio_dev.pin); 
}

/* 设置MDIO的gpio引脚为输入引脚 */
static void MDIO_IN(void)
{
    gpio_cblk_t gpio_dev;
    gpio_dev.pin = MDIO;
    GPIO_INPUT_ENABLE(gpio_dev.pin); 
}

/* MDC输出高电平，在MDC设置为输出后调用 */
static void MDC_H(void)
{
    gpio_cblk_t gpio_dev;

    gpio_dev.pin = MDC;
    gpio_dev.value = 1;
    gpio_direction_output_value(gpio_dev.pin,gpio_dev.value);
}

/* MDC输出低电平，在MDC设置为输出后调用 */
static void MDC_L(void)
{
    gpio_cblk_t gpio_dev;

    gpio_dev.pin = MDC;
    gpio_dev.value = 0;
    gpio_direction_output_value(gpio_dev.pin,gpio_dev.value);
}

/* 获得MDIO的数据，只获得一个bit */
static int GET_MDIO(void)
{
    gpio_cblk_t gpio_dev;

//    printk("jiangyibo 1 %0x\n",regRead32(CR_GPIO_DATA));
    gpio_dev.pin = MDIO;
    gpio_dev.value = gpio_get_value_ok(gpio_dev.pin);

    return gpio_dev.value;
}

/* 设置MDIO的数据，一个bit */
static void SET_MDIO(int val)
{
    gpio_cblk_t gpio_dev;

    gpio_dev.pin = MDIO;
    gpio_dev.value = val;
    gpio_direction_output_value(gpio_dev.pin,gpio_dev.value);
}


/* MDIO发送一个bit的数据，MDIO必须已经被配置为输出 */
static void mdio_bb_send_bit(int val)
{
    MDC_OUT();    
    SET_MDIO(val);
    ndelay(MDIO_DELAY);
    MDC_L();
    ndelay(MDIO_DELAY);
    MDC_H();
}

static void mdio_bb_delay(int val)
{
    ndelay(MDIO_DELAY);
    MDC_L();
    ndelay(MDIO_DELAY);
    MDC_H();
}


/*  MDIO 获取一个bit的数据，MDIO必须已经被配置为输入. */   
static int mdio_bb_get_bit(void)
{
    int value;

 //   MDC_OUT();    
    ndelay(MDIO_DELAY);
    MDC_L();
    ndelay(MDIO_READ_DELAY);
//    ndelay(MDIO_DELAY);
    MDC_H();
    
    value = GET_MDIO();
    
    return value;
}

 /*  
  *  MDIO发送一个数据,MDIO 必须被配置为输出模式. 
  *  value:要发送的数据
  *  bits:数据的位数
  *  
  *  */  
static void mdio_bb_send_num(unsigned int value ,int bits)
{
    int i;
    MDIO_OUT();
    
    for(i = bits - 1; i >= 0; i--)
        mdio_bb_send_bit((value >> i) & 1);
}

 /*  
  *  MDIO获取一个数据,MDIO 必须被配置为输入模式. 
  *  bits:获取数据的位数
  *  
  *  */  
static int mdio_bb_get_num(int bits)
{
    int i;
    int ret = 0;
    for(i = bits - 1; i >= 0; i--)
    {
        ret <<= 1;
        ret |= mdio_bb_get_bit(); 
    }

    return ret;
}



/*  Utility to send the preamble, address, and
*   register (common to read and write). 
*/
static void mdio_bb_cmd(int op,int phy,int reg)
{
    int i = 0 ;
    MDIO_OUT();  //设置MDIO引脚为输出引脚

    /*发送32bit的1，这个帧前缀域不是必须的，某些物理层芯片的MDIO操作就没有这个域*/
    for(i = 0; i < 32; i++)
        mdio_bb_send_bit(1);


    /* 发送开始位(01),和读操作码(10),写操作码(01)
     * Clause 45 操作，开始位是(00),(11)为读,(10)为写
    */

#if MDIO_C45_TEST
    mdio_bb_send_bit(0);
    if(op & MDIO_C45)
        mdio_bb_send_bit(0);
    else
        mdio_bb_send_bit(1);


#else
    mdio_bb_send_bit(0);
    mdio_bb_send_bit(1);
    
#endif
    mdio_bb_send_bit((op >> 1) & 1);
    mdio_bb_send_bit((op >> 0) & 1);

    mdio_bb_send_num(phy,5);
    mdio_bb_send_num(reg,5);

}

static int mdio_bb_cmd_addr(int phy,int addr)
{
    unsigned int dev_addr = (addr >> 16) & 0x1F;
    unsigned int reg = addr & 0xFFFF;

    mdio_bb_cmd(MDIO_C45_ADDR,phy,dev_addr);

    /*  send the turnaround (10) */  
    mdio_bb_send_bit(1);
    mdio_bb_send_bit(0);

    mdio_bb_send_num(reg,16);
    
    MDIO_IN();
    mdio_bb_get_bit();

    return dev_addr;
}

void mdio_set_turnaround(void)
{
    int i = 0;

    MDIO_IN();
    MDC_OUT();
    for(i=0;i<2;i++)
    {
        ndelay(MDIO_DELAY);
        MDC_L();
        ndelay(MDIO_DELAY);
        MDC_H();
    }
}





static unsigned int mdio_bb_read(int phy,int reg)
{
    unsigned int ret,i;

#if MDIO_C45_TEST
    /* 寄存器是否满足有C45标志 */
    if(reg & MII_ADDR_C45)
    {
        reg = mdio_bb_cmd_addr(phy,reg);
        mdio_bb_cmd(MDIO_C45_READ,phy,reg);
    }
    else
        mdio_bb_cmd(MDIO_READ,phy,reg);
#else
        mdio_bb_cmd(MDIO_READ,phy,reg);
#endif
    MDIO_IN();

	mdio_bb_delay(1);

    //mdio_set_turnaround();
    /*  check the turnaround bit: the PHY should be driving it to zero */ 
    if(mdio_bb_get_bit() != 0)
    {
        
        /* PHY didn't driver TA low -- flush any bits it may be trying to send*/
        for(i = 0; i < 32; i++)
            mdio_bb_get_bit();
        return 0xFFFF;
    }
	

    ret = mdio_bb_get_num(16);
    mdio_bb_get_bit();
    
    return ret;
}



static int mdio_bb_write(unsigned int phy,unsigned int reg,unsigned int val)
{
#if MDIO_C45_TEST
    if(reg & MII_ADDR_C45)
    {
        reg = mdio_bb_cmd_addr(phy,reg);
        mdio_bb_cmd(MDIO_C45_WRITE,phy,reg);
    }
    else
        mdio_bb_cmd(MDIO_WRITE,phy,reg);
#else
        mdio_bb_cmd(MDIO_WRITE,phy,reg);
#endif


#if 1
    /*  send the turnaround (10) */  
    mdio_bb_send_bit(1);
    mdio_bb_send_bit(0);
#else
    mdio_set_turnaround();
#endif
    mdio_bb_send_num(val,16);
    
    MDIO_IN();
    //mdio_bb_get_bit();

    return 0;
}


static unsigned int mdio_8337_read(int reg,int addr)
{
    int hAddr = (addr&0x7fe00)>>9;
	
	int lAddr_phy = ((addr&0x3ff)>>6)|0x10;

	int lAddr_reg_1 = (((addr&0x3c)>>2)<<1);

	int lAddr_reg_2 = (((addr&0x3c)>>2)<<1)|0x1;

	int ret = 0;

	int ret_1 = 0;
	
	mdio_bb_write(0x18,0x0,hAddr);

    ret =	mdio_bb_read(lAddr_phy,lAddr_reg_1);

	ret_1 = mdio_bb_read(lAddr_phy,lAddr_reg_2);

	
	return  ret|(ret_1<<16);

}




static unsigned int mdio_8337_write(int addr,int value)
{
    int hAddr = (addr&0x7fe00)>>9;
	
	int lAddr_phy = ((addr&0x3ff)>>6)|0x10;

	int lAddr_reg_1 = (((addr&0x3c)>>2)<<1);

	int lAddr_reg_2 = (((addr&0x3c)>>2)<<1)|0x1;

	int ret = 0;

	int ret_1 = 0;
	
	mdio_bb_write(0x18,0x0,hAddr);

    ret =	mdio_bb_write(lAddr_phy,lAddr_reg_1,value&0xffff);

	ret_1 = mdio_bb_write(lAddr_phy,lAddr_reg_2,(value>>16)&0xffff);

	
	return  ret|(ret_1<<16);

}




#define MODULE_VERS "1.0"  


#define MODULE_NAME "procfs_example"  

#define PROC_NAME "8337"  

  
#define FOOBAR_LEN 8  
  
struct fb_data_t {  
    int type; 
    char name[4];  
    char value[4];  
};  
  
  
static struct proc_dir_entry *example_dir, *foo_file;    
  
struct fb_data_t foo_data;  

static int proc_read_boma(void)  
{  

	int temp ;

	//HIGH_SDA();

	temp = LED_GET_GPIO_DATA(GPIO_BOMA_1)>>GPIO_BOMA_1;


	temp =(temp<<1)| (LED_GET_GPIO_DATA(GPIO_BOMA_2)>>GPIO_BOMA_2);


	temp =(temp<<1)| (LED_GET_GPIO_DATA(GPIO_BOMA_3)>>GPIO_BOMA_3);


	temp =(temp<<1)| (LED_GET_GPIO_DATA(GPIO_BOMA_4)>>GPIO_BOMA_4);

	return temp;
	
}  




static int proc_read_sfp(void)  
{  

	int temp ;

	//HIGH_SDA();

	temp = LED_GET_GPIO_DATA(GPIO_SFP_R_WUGUANG_1)>>GPIO_SFP_R_WUGUANG_1;


	temp =(temp<<1)| (LED_GET_GPIO_DATA(GPIO_SFP_R_WUGUANG_2)>>GPIO_SFP_R_WUGUANG_2);


	return temp;
	
}  



static unsigned int proc_sfp_write(int addr,int value)
{

	int ret = 0;
	if(addr == 1)
	{
		if(value == 1){
			HIGH_NORMAL(GPIO_SFP_SEND_1);
		}
		else 
		{
			LOW_NORMAL(GPIO_SFP_SEND_1); 
		}

	}else {

		if(value == 1){
			HIGH_NORMAL(GPIO_SFP_SEND_2);
		}
		else 
		{
			LOW_NORMAL(GPIO_SFP_SEND_2); 
		}

	}
	
	return  0;

}


static unsigned int proc_reset_write(int addr,int value)
{

	int ret = 0;
	if(addr == 1)
	{
		if(value == 1){
			HIGH_NORMAL(GPIO_8337_RESET);
		}
		else 
		{
			LOW_NORMAL(GPIO_8337_RESET); 
		}

	}else {

		if(value == 1){
			HIGH_NORMAL(GPIO_8033_RESET);
		}
		else 
		{
			LOW_NORMAL(GPIO_8033_RESET); 
		}

	}
	
	return  0;

}




static char recv_msg[20];



static int proc_read_foobar(char *page, char **start,  
				off_t off, int count,	
				int *eof, void *data)  
{  
	u8	regAddr;
	int err = 0;
	int result;
	int len = sizeof(struct fb_data_t);  
	struct fb_data_t *fb_data = (struct fb_data_t *)data;  

	printk("jiangyibo read %d %x %x\n",fb_data->type,*(int *)fb_data->name,*(int *)fb_data->value);

    if(fb_data->type==1)
    {
        result = mdio_8337_write(*(int *)fb_data->name,*(int *)fb_data->value);
    }else if(fb_data->type==2){
		result = mdio_8337_read(1,*(int *)fb_data->name);
    }else if(fb_data->type==3){
		result = proc_read_boma();
    }else if(fb_data->type==4){
		result = proc_read_sfp();
    }else if(fb_data->type==5){
		result = proc_sfp_write(*(int *)fb_data->name,*(int *)fb_data->value);
    }else if(fb_data->type==6){
		result = proc_reset_write(*(int *)fb_data->name,*(int *)fb_data->value);
    }


	printk("jiangyibo %0x \n",result);
	
	fb_data->type=result;
	
    *(int *)page = result;
	//copy_to_user(page, &result, 4);
	return 4;

}  
				  

  
  
static int proc_write_foobar(struct file *file,  
                 const char *buffer,  
                 unsigned long count,   
                 void *data)  
{  
    int len;  
    struct fb_data_t *fb_data = (struct fb_data_t *)data;  
  
    if(count > FOOBAR_LEN)  
        len = FOOBAR_LEN;  
    else  
        len = count;  

	memset(fb_data->name,0,FOOBAR_LEN+1);

    if(copy_from_user(fb_data, buffer, 12))  
        return -EFAULT;  

    fb_data->value[len] = '\0';  
  
    return len;  
}  
  
static int __init pcf8574_init(void)//放后面，因为 misc_register 要包含前面的内容
{
/*
    int ret;

	printk("jiangyibo init\n");

    ret = misc_register(&pcf8574_miscdev);
    if (ret) {
        printk("misc_register error\n");
        return ret;
    }
    
    printk(KERN_INFO DRV_NAME " ver " DRV_VERSION" init\n");
*/
	  int rv = 0;  
	  
		/* create directory */	
		example_dir = proc_mkdir(MODULE_NAME, NULL);  
		if(example_dir == NULL) {  
			rv = -ENOMEM;  
			goto out;  
		}  
	  
		/* create foo and bar files using same callback  
		 * functions   
		 */  
		foo_file = create_proc_entry(PROC_NAME, 0644, example_dir);  
		if(foo_file == NULL) {	
			rv = -ENOMEM;  
			goto no_foo;  
		}  
	  
		strcpy(foo_data.name, "foo");  
		strcpy(foo_data.value, "foo");	
		foo_file->data = &foo_data;  
		foo_file->read_proc = proc_read_foobar;  
    	foo_file->write_proc = proc_write_foobar;  

		op_pcf8574_init();	 
		/* everything OK */  
		printk(KERN_INFO "%s %s initialised\n",  
			   MODULE_NAME, MODULE_VERS);  
		return 0;  
	  
	 
	no_foo:  
		remove_proc_entry("jiffies", example_dir);	
	 
	out:  
		return rv;	

}

module_init(pcf8574_init);

static void __exit pcf8574_exit(void)
{
/*    int ret;

    ret = misc_deregister(&pcf8574_miscdev);//注销
    if (ret) {
        printk("misc_deregister error\n");
        return ;
    }

    printk(KERN_INFO DRV_NAME " ver " DRV_VERSION" exit\n");
    */
    remove_proc_entry(PROC_NAME, example_dir);    
    remove_proc_entry(MODULE_NAME, NULL);  
  
    printk(KERN_INFO "%s %s removed\n",  
           MODULE_NAME, MODULE_VERS); 

}
module_exit(pcf8574_exit);

MODULE_DESCRIPTION(DRV_DESC);//描述
MODULE_VERSION(DRV_VERSION);//版本
MODULE_AUTHOR("tingpan <smbx-ztbz@cnblogs.com>");//作者
MODULE_LICENSE("GPL v2");//协议



